/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.andremion.louvre.loader;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * AsyncTaskLoader
 *
 * @param <Param>  Parameter
 * @param <Result> Result
 */
public abstract class AsyncTaskLoader<Param, Result> {
    private final Executor sDefaultExecutor = Executors.newCachedThreadPool();
    private final WorkerRunnable<Param, Result> mWorker;
    private final FutureTask<Result> mFuture;

    private final AtomicBoolean mCancelled = new AtomicBoolean(false);
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean(false);

    public AsyncTaskLoader() {
        mWorker = new WorkerRunnable<Param, Result>() {
            @Override
            public Result call() {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    result = doInBackground(mParams);
                } catch (ExceptionInInitializerError runtimeException) {
                    mCancelled.set(true);
                    throw runtimeException;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    postResultIfNotInvoked(null);
                } catch (ExecutionException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

    /**
     * doInBackground
     *
     * @param mP Param
     * @return Result
     */
    protected abstract Result doInBackground(Param... mP);

    /**
     * isCancelled
     *
     * @return Boolean
     */
    public final boolean isCancelled() {
        return mCancelled.get();
    }

    /**
     * cancel
     *
     * @param mayInterruptIfRunning Boolean
     * @return Boolean
     */
    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }

    /**
     * get
     *
     * @return Result
     * @throws InterruptedException InterruptedException
     * @throws ExecutionException   ExecutionException
     */
    public final Result get() throws InterruptedException, ExecutionException {
        return mFuture.get();
    }

    /**
     * get
     *
     * @param timeout Long
     * @param unit    TimeUnit
     * @return Result
     * @throws InterruptedException InterruptedException
     * @throws ExecutionException   ExecutionException
     * @throws TimeoutException     TimeoutException
     */
    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
            ExecutionException, TimeoutException {
        return mFuture.get(timeout, unit);
    }

    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

    /**
     * postResult
     *
     * @param result Result
     */
    protected void postResult(Result result) {
    }

    /**
     * onPreExecute
     */
    protected void onPreExecute() {
    }

    /**
     * AsyncTaskLoader
     *
     * @param mP Param
     * @return AsyncTaskLoader<Param, Result>
     */
    public final AsyncTaskLoader<Param, Result> execute(Param... mP) {
        return executeOnExecutor(sDefaultExecutor, mP);
    }

    /**
     * AsyncTaskLoader
     *
     * @param mP   Param
     * @param exec Executor
     * @return AsyncTaskLoader
     */
    public final AsyncTaskLoader<Param, Result> executeOnExecutor(Executor exec, Param... mP) {
        onPreExecute();
        mWorker.mParams = mP;
        exec.execute(mFuture);
        return this;
    }

    /**
     * WorkerRunnable
     *
     * @param <Result> Result
     * @param <P>      Object
     */
    private static abstract class WorkerRunnable<P, Result> implements Callable<Result> {
        P[] mParams;
    }
}
